package com.dyna.digit.process.strategy.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import com.dyna.constants.DictTypeConstant;
import com.dyna.constants.RedisKeyConstant;
import com.dyna.core.exception.CustomException;
import com.dyna.core.utils.ObjectUtil;
import com.dyna.digit.process.domain.entity.DigitDeclare;
import com.dyna.digit.process.domain.entity.DigitDeclareBaseJsl;
import com.dyna.digit.process.domain.entity.DigitDeclareBaseYwl;
import com.dyna.digit.process.domain.entity.DigitProject;
import com.dyna.digit.process.domain.form.DigitDeclareForm;
import com.dyna.digit.process.mapper.DigitDeclareMapper;
import com.dyna.digit.process.mapper.DigitProjectMapper;
import com.dyna.digit.process.strategy.AbstractFlowHandler;
import com.dyna.domain.resp.R;
import com.dyna.domain.resp.ResultCode;
import com.dyna.enums.*;
import com.dyna.foundation.domain.entity.SysDictData;
import com.dyna.foundation.feign.FoundationDictApi;
import com.dyna.utils.RdsUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 项目申报
 * @author zhoucaiwang
 * @date 2023/11/19
 */

@Slf4j
@Component
public class DigitDeclaredFlow extends AbstractFlowHandler {

    private final static List<String> dictTypeList = new ArrayList<>();
    static {
        dictTypeList.add(DictTypeConstant.PROJ_BUILD_CATEGORY);
        dictTypeList.add(DictTypeConstant.PROJ_YWL_BUILD_CATEGORY);
        dictTypeList.add(DictTypeConstant.PROJ_CATEGORY);
        dictTypeList.add(DictTypeConstant.PROJ_CONFIDENTIALITY);
        dictTypeList.add(DictTypeConstant.PROJ_USER_RANGE);
        dictTypeList.add(DictTypeConstant.PROJ_NET_ENV);
        dictTypeList.add(DictTypeConstant.PROJ_PUBLISH);
        dictTypeList.add(DictTypeConstant.PROJ_CONS_CLASS);
        dictTypeList.add(DictTypeConstant.CONFIRM);
        dictTypeList.add(DictTypeConstant.PROJ_EQUAL_INSURANCE_LEVEL);
        dictTypeList.add(DictTypeConstant.PRO_PENETRATION_HIERARCHY);
    }

    private final DigitProjectMapper digitProjectMapper;
    private final DigitDeclareMapper digitDeclareMapper;
    private final PlatformTransactionManager transactionManager;
    private final FoundationDictApi foundationDictApi;

    @Autowired
    public DigitDeclaredFlow(DigitProjectMapper digitProjectMapper,
                             DigitDeclareMapper digitDeclareMapper,
                             PlatformTransactionManager transactionManager,
                             FoundationDictApi foundationDictApi
    ) {
        this.digitProjectMapper = digitProjectMapper;
        this.digitDeclareMapper = digitDeclareMapper;
        this.transactionManager = transactionManager;
        this.foundationDictApi = foundationDictApi;
    }

    @Override
    public DigitFlowStatusEnum getDigitFlowEnum() {
        return DigitFlowStatusEnum.DECLARED;
    }

    @Override
    public String flow() {
        return DigitFlowStatusEnum.DECLARED.getDesc();
    }

    @Override
    public Object detail(Integer projectId) {
        return digitDeclareMapper.detail(projectId);
    }

    @Override
    public Boolean add(Object project) {
        DigitDeclareForm form = Convert.convert(DigitDeclareForm.class, project);
        if (ObjectUtil.isNull(form)) {
            throw new CustomException(ResultCode.PARAM_VALID_ERROR.getMessage());
        }else {
            // 校验参数
            try {
                this.checkValidation(form);
            }catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new CustomException(e.getMessage());
            }
        }
        // 根据项目名称加分布式锁进行限流
        String key = RedisKeyConstant.REDIS_LIMITER_KEY + form.getDigitDeclare().getProjectName();
        Boolean lock = RdsUtil.lock(key, form.getDigitDeclare().getProjectName(), NumberEnums.FIVE.getCode(), TimeUnit.SECONDS);
        if (!lock) {
            throw new CustomException(DigitErrorMsgEnum.REQUEST_LIMIT_MSG.getMsg());
        }
        // 使用redis自增作为项目id
        Integer projectId = RdsUtil.incr(RedisKeyConstant.PROJ_INCR_KEY);
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        // 事务隔离级别
        definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus transaction = transactionManager.getTransaction(definition);
        try {
            // 保存项目信息主表
            digitProjectMapper.saveProject(new DigitProject(projectId));
            // 保存申报信息
            form.getDigitDeclare().setProjectId(projectId);
            digitDeclareMapper.saveDeclare(form.getDigitDeclare());
            if (DigitProjectTypeEnum.JSL.getValue().equals(form.getDigitDeclare().getProjectType())) {
                form.getJsl().setProjectId(projectId);
                digitDeclareMapper.saveJsl(form.getJsl());
            }else {
                form.getYwl().setProjectId(projectId);
                digitDeclareMapper.saveYwl(form.getYwl());
            }
            // 保存申报金额
            form.getAmounts().setProjectId(projectId);
            form.getAmounts().setType(DigitAmountTypeEnum.DECLARE_AMOUNT.getType());
            digitDeclareMapper.saveAmount(form.getAmounts());
            // 保存申报附件
            form.getFiles().setProjectId(projectId);
            digitDeclareMapper.saveFile(form.getFiles());
            transactionManager.commit(transaction);
        }catch (Exception e) {
            transactionManager.rollback(transaction);
            throw new CustomException(e.getMessage());
        }
        return Boolean.TRUE;
    }

    @Override
    public Boolean edit(DigitDeclareForm project) {
        Integer projectId = project.getProjectId();
        DigitDeclareForm form = Convert.convert(DigitDeclareForm.class, project);
        if (ObjectUtil.isNull(form)) {
            throw new CustomException(ResultCode.PARAM_VALID_ERROR.getMessage());
        }else {
            // 校验参数
            try {
                this.checkValidation(form);
            }catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new CustomException(e.getMessage());
            }
        }
        // 根据项目名称加分布式锁进行限流
        String key = RedisKeyConstant.REDIS_LIMITER_KEY + form.getDigitDeclare().getProjectName();
        Boolean lock = RdsUtil.lock(key, form.getDigitDeclare().getProjectName(), NumberEnums.FIVE.getCode(), TimeUnit.SECONDS);
        if (!lock) {
            throw new CustomException(DigitErrorMsgEnum.REQUEST_LIMIT_MSG.getMsg());
        }
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        // 事务隔离级别
        definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus transaction = transactionManager.getTransaction(definition);
        try {
            // 保存申报信息
            form.getDigitDeclare().setProjectId(projectId);
            digitDeclareMapper.delDeclare(projectId);
            digitDeclareMapper.saveDeclare(form.getDigitDeclare());
            if (DigitProjectTypeEnum.JSL.getValue().equals(form.getDigitDeclare().getProjectType())) {
                form.getJsl().setProjectId(projectId);
                digitDeclareMapper.delJsl(projectId);
                digitDeclareMapper.saveJsl(form.getJsl());
            }else {
                form.getYwl().setProjectId(projectId);
                digitDeclareMapper.delYwl(projectId);
                digitDeclareMapper.saveYwl(form.getYwl());
            }
            // 保存申报金额
            form.getAmounts().setProjectId(projectId);
            form.getAmounts().setType(DigitAmountTypeEnum.DECLARE_AMOUNT.getType());
            digitDeclareMapper.delAmount(projectId);
            digitDeclareMapper.saveAmount(form.getAmounts());
            // 保存申报附件
            form.getFiles().setProjectId(projectId);
            digitDeclareMapper.delFile(projectId);
            digitDeclareMapper.saveFile(form.getFiles());
            transactionManager.commit(transaction);
        }catch (Exception e) {
            transactionManager.rollback(transaction);
            throw new CustomException(e.getMessage());
        }
        return Boolean.TRUE;
    }

    /**
     * 因为要根据类型校验建设类或者运维类，因此不能再controller层进行校验，需要通过ValidFactory
     * @param data 项目申报数据
     */
    private void checkValidation(DigitDeclareForm data) {
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        // digit_declare校验
        Set<ConstraintViolation<DigitDeclare>> validate1 = validator.validate(data.getDigitDeclare());
        for (ConstraintViolation<DigitDeclare> declareValidation : validate1) {
            throw new CustomException(declareValidation.getMessage());
        }
        //加载字典数据
        R<Map<String, List<SysDictData>>> mapR = foundationDictApi.getBatchSysDictList(dictTypeList);
        if (DigitProjectTypeEnum.JSL.getValue().equals(data.getDigitDeclare().getProjectType())) {
            Optional.ofNullable(data.getJsl()).orElseThrow(()-> new CustomException("申报建设类信息不能为空"));
            // 校验是否
            Set<ConstraintViolation<DigitDeclareBaseJsl>> validate2 = validator.validate(data.getJsl());
            for (ConstraintViolation<DigitDeclareBaseJsl> declareValidation : validate2) {
                throw new CustomException(declareValidation.getMessage());
            }
            // 建设类申报信息
            this.checkJslValidation(mapR.getData(),data.getJsl());
            // 单独校验校验建设类别
            this.checkObjExist(mapR.getData().get(DictTypeConstant.PROJ_BUILD_CATEGORY), data.getDigitDeclare().getProjBuildCategory(), "建设类别");
        }else {
            // 运维类申报信息
            Optional.ofNullable(data.getYwl()).orElseThrow(()-> new CustomException("申报运维类信息不能为空"));
            this.checkYwlValidation(mapR.getData(),data.getYwl());
            // 单独校验
            this.checkObjExist(mapR.getData().get(DictTypeConstant.PROJ_YWL_BUILD_CATEGORY), data.getDigitDeclare().getProjBuildCategory(), "建设类别");
        }
    }

    /**
     * 建设类参数校验
     * @param jsl 建设类数据
     */
    private void checkJslValidation(Map<String, List<SysDictData>> dictMap, DigitDeclareBaseJsl jsl) {
        // 项目分类
        this.checkListExist(dictMap.get(DictTypeConstant.PROJ_CATEGORY), jsl.getProjCategory(), "项目分类");
        // 密级
        this.checkObjExist(dictMap.get(DictTypeConstant.PROJ_CONFIDENTIALITY), jsl.getProjIsConfidentiality(), "密级");
        // 用户范围
        this.checkListExist(dictMap.get(DictTypeConstant.PROJ_USER_RANGE), jsl.getProjUserRange(), "用户范围");
        // 网络环境
        this.checkListExist(dictMap.get(DictTypeConstant.PROJ_NET_ENV), jsl.getProjNetEnv(), "网络环境");
        // 发布端
        this.checkListExist(dictMap.get(DictTypeConstant.PROJ_PUBLISH), jsl.getProjPublish(), "发布端");
        // 建设层级
        this.checkObjExist(dictMap.get(DictTypeConstant.PROJ_CONS_CLASS), jsl.getProjConsClass(), "建设层级");
        // 等保级别
        this.checkObjExist(dictMap.get(DictTypeConstant.PROJ_EQUAL_INSURANCE_LEVEL), jsl.getProjEqualInsuranceLevel(), "等保级别");
    }

    /**
     * 运维类参数校验
     * @param ywl 运维类数据
     */
    private void checkYwlValidation(Map<String, List<SysDictData>> dictMap, DigitDeclareBaseYwl ywl) {
        // 项目分类
        this.checkListExist(dictMap.get(DictTypeConstant.PROJ_CATEGORY), ywl.getProjCategory(), "项目分类");
        // 密级
        this.checkObjExist(dictMap.get(DictTypeConstant.PROJ_CONFIDENTIALITY), ywl.getProjIsConfidentiality(), "密级");
        // 用户范围
        this.checkListExist(dictMap.get(DictTypeConstant.PROJ_USER_RANGE), ywl.getProjUserRange(), "用户范围");
        // 网络环境
        this.checkListExist(dictMap.get(DictTypeConstant.PROJ_NET_ENV), ywl.getProjNetEnv(), "网络环境");
        // 发布端
        this.checkListExist(dictMap.get(DictTypeConstant.PROJ_PUBLISH), ywl.getProjPublish(), "发布端");
        // 建设层级
        this.checkObjExist(dictMap.get(DictTypeConstant.PROJ_CONS_CLASS), ywl.getProjConsClass(), "建设层级");
        // 贯通层级
        this.checkObjExist(dictMap.get(DictTypeConstant.PRO_PENETRATION_HIERARCHY), ywl.getProPenetrationHierarchy(), "贯通层级");
        // 等保级别
        this.checkObjExist(dictMap.get(DictTypeConstant.PROJ_EQUAL_INSURANCE_LEVEL), ywl.getProjEqualInsuranceLevel(), "等保级别");
    }

    /**
     * 检查参数是否存在
     * @param data 字典数据
     * @param value 校验字段参数值
     * @param errorField 错误字段
     */
    private void checkObjExist(List<SysDictData> data, Integer value, String errorField) {
        if (CollUtil.isEmpty(data)) {
            log.error("字典数据异常");
            throw new CustomException("字典数据异常");
        }
        // 不等于空并且没有匹配到，则说明参数不在字典中
        if (ObjectUtil.isNotNull(value) && data.stream().noneMatch(f-> f.getDictId().equals(value))) {
            throw new CustomException(errorField + "参数异常");
        }
    }

    /**
     * 检查数组中每个元素是否都存在
     * @param data 字典数据
     * @param valueList 校验字段参数值
     * @param errorField 错误字段
     */
    private void checkListExist(List<SysDictData> data, List<Integer> valueList, String errorField) {
        if (CollUtil.isEmpty(data)) {
            log.error("字典数据异常");
            throw new CustomException("字典数据异常");
        }
        if (CollUtil.isNotEmpty(valueList)) {
            for (Integer value : valueList) {
                if (data.stream().noneMatch(f-> f.getDictId().equals(value))) {
                    throw new CustomException(errorField + "参数异常");
                }
            }
        }
    }

}
